﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using Common.Logging;
using Newtonsoft.Json;
using OutDbContext;
using Senparc.Weixin;
using Senparc.Weixin.Entities;
using Senparc.Weixin.Exceptions;
using Senparc.Weixin.MP.AdvancedAPIs;
using Senparc.Weixin.MP.CommonAPIs;
using Senparc.Weixin.MP.Helpers;
using Senparc.Weixin.MP.TenPayLib;
using User.Api.Helpers.TenPayV2.WxPay;

namespace User.Api.Helpers
{
    public class TenpayV2Helper
    {
        private static readonly ILog Logger = LogManager.GetLogger(typeof(TenpayV2Helper));

        private static TenPayInfo _tenPayInfo;

        public static TenPayInfo TenPayInfo
        {
            get
            {
                if (_tenPayInfo == null)
                {
                    _tenPayInfo =
                        TenPayInfoCollection.Data[ConfigurationManager.AppSettings["WeixinPay_PartnerId"]];
                }
                return _tenPayInfo;
            }
        }

        /// <summary>
        /// 生成公众号网页支付的请求包
        /// </summary>
        /// <param name="tradeName">支付单名称，128 字节以下，如：广东妇保就诊挂号，广东妇保就诊缴费</param>
        /// <param name="tradeNo">商户系统内部的订单号， 32个字符内，可包含字母，确保在商户系统唯一</param>
        /// <param name="totalFee">订单总金额， 单位为分</param>
        /// <param name="clientIp">指用户浏览器端IP，不是商户服务器，格式为 IPV4</param>
        /// <param name="startTime">订单生成时间，格式为 yyyyMMddHHmmss 如2009年 12 月 25 日 9点 10 分 10 秒表示为 20091225091010，时区为 GMT+8 beijing；该时间取自商户服务器；</param>
        /// <param name="expireTime">订单失效时间，格式为 yyyyMMddHHmmss 如2009年 12 月 25 日 9点 10 分 10 秒表示为 20091225091010，时区为 GMT+8 beijing；该时间取自商户服务器；</param>
        /// <param name="notifyControllerAction">支付成功通知的 controller action , 如 Reg/PaySucceed</param>
        /// <returns></returns>
        public static string GenerateWxPayParameter(string tradeName, string tradeNo, string totalFee, 
            string clientIp, string startTime, string expireTime,string notifyControllerAction)
        {
            var wxPayHelper = new TenPayV2.WxPay.WxPayHelper();
            //先设置基本信息
            wxPayHelper.SetAppId(TenPayInfo.AppId);
            wxPayHelper.SetAppKey(TenPayInfo.AppKey);
            wxPayHelper.SetPartnerKey(TenPayInfo.Key);
            wxPayHelper.SetSignType("SHA1");

            //设置请求package信息
            wxPayHelper.SetParameter("bank_type", "WX");
            wxPayHelper.SetParameter("body", tradeName);
            wxPayHelper.SetParameter("partner", TenPayInfo.PartnerId);
            wxPayHelper.SetParameter("out_trade_no", tradeNo);
            wxPayHelper.SetParameter("total_fee", totalFee);
            //wxPayHelper.SetParameter("total_fee", "1");
            wxPayHelper.SetParameter("fee_type", "1");
            wxPayHelper.SetParameter("notify_url", TenPayInfo.TenPayNotify + notifyControllerAction);
            wxPayHelper.SetParameter("spbill_create_ip", String.IsNullOrEmpty(clientIp) ? "127.0.0.1" : clientIp);
            wxPayHelper.SetParameter("time_start", startTime);
            wxPayHelper.SetParameter("time_expire", expireTime);
            wxPayHelper.SetParameter("input_charset", "UTF-8");

            return wxPayHelper.CreateBizPackage();            
        }

        /// <summary>
        /// 发送发货通知
        /// </summary>
        /// <param name="openId">用户的 OpenId</param>
        /// <param name="transId">交易单号</param>
        /// <param name="outTradeNo">第三方订单号 （交易号和第三方订单号只需填写一个）</param>
        /// <param name="deliverStatus">发货状态，1 表明成功，0 表明失败，失败时需要在 deliver_msg 填上失败原因</param>
        /// <param name="deliverMsg">发货状态信息，失败时可以填上 Ascii 编码的错误提示信息，比如“Refunded”</param>
        /// <param name="retryCount">重试次数，解决 Senparc Accesstoken 问题</param>
        /// <returns></returns>
        public static DeliverNotifyResult DoDeliverNotify(string openId, string transId, string outTradeNo,
            string deliverStatus, string deliverMsg, int retryCount = 0)
        {
            var deliverTimestamp = ((DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000000).ToString();
            var parameters = new Dictionary<string, string>();
            parameters.Add("appid", TenPayInfo.AppId);
            parameters.Add("openid", openId);
            parameters.Add("transid", transId);
            parameters.Add("out_trade_no", outTradeNo);
            parameters.Add("deliver_timestamp", deliverTimestamp);
            parameters.Add("deliver_status", deliverStatus);
            parameters.Add("deliver_msg", deliverMsg);

            var wxPayHelper = new WxPayHelper();
            //先设置基本信息
            wxPayHelper.SetAppId(TenPayInfo.AppId);
            wxPayHelper.SetAppKey(TenPayInfo.AppKey);
            wxPayHelper.SetPartnerKey(TenPayInfo.Key);
            wxPayHelper.SetSignType("SHA1");
            var appSignature = wxPayHelper.GetBizSign_utf8(parameters);

            WxJsonResult result;

            try
            {
                result = TenPay.Delivernotify(TenPayInfo.AppId, openId, transId, outTradeNo,
                                 deliverTimestamp, deliverStatus, deliverMsg, appSignature, "SHA1");                
            }
            catch (ErrorJsonResultException ex)
            {
                Logger.ErrorFormat("发送微信发货通知时 Separc Weixin SDK 出错，信息：{0}", JsonConvert.SerializeObject(ex));

                if (ex.JsonResult.errcode == ReturnCode.获取access_token时AppSecret错误或者access_token无效
                    || ex.JsonResult.errcode == ReturnCode.不合法的access_token)
                {
                    var appId = AccessTokenContainer.GetFirstOrDefaultAppId();
                    AccessTokenContainer.GetAccessTokenResult(appId, true);

                    if (retryCount <= 1)
                    {
                        retryCount++;
                        return DoDeliverNotify(openId, transId, outTradeNo, deliverStatus, deliverMsg, retryCount); 
                    }
                }

                return new DeliverNotifyResult
                {
                    ErrorCode = -100,
                    ErrorMsg = "发生异常"
                };
            }
            catch (Exception ex)
            {
                Logger.ErrorFormat("发送微信发货通知时出错，信息：{0}", JsonConvert.SerializeObject(ex));
                return new DeliverNotifyResult
                {
                    ErrorCode = -100,
                    ErrorMsg = "发生异常"
                };
            }

            return new DeliverNotifyResult
            {
                ErrorCode = result.errcode.ToInt32OrDefault(),
                ErrorMsg = result.errmsg
            };
        }

        /// <summary>
        /// 执行微信退款操作
        /// </summary>
        /// <param name="openId">被退款人OpenId</param>
        /// <param name="outTradeNo">
        /// out_trade_no
        /// 商户订单号
        /// 商户系统内部的订单号, out_trade_no 和
        /// transaction_id 至少一个必填，同时存在时
        /// transaction_id 优先
        /// </param>
        /// <param name="transactionId">
        /// transaction_id
        /// 财付通订单号
        /// 财付通交易号, out_trade_no 和
        /// transaction_id 至少一个必填，同时存在时
        /// transaction_id 优先
        /// </param>
        /// <param name="outRefundNo">
        /// out_refund_no
        /// 商户退款单号
        /// 商户退款单号，32 个字符内、可包含字母,确
        /// 保在商户系统唯一。同个退款单号多次请求
        /// 财付通当一个单处理，只会退一次款。如果出
        /// 现退款不成功，请采用原退款单号重新发起，
        /// 避免出现重复退款。
        /// </param>
        /// <param name="totalFee">
        /// total_fee
        /// 总金额
        /// 订单总金额，单位为分
        /// </param>
        /// <param name="refundFee">
        /// refund_fee
        /// 退款金额
        /// 退款总金额,单位为分,可以做部分退款
        /// </param>
        /// <returns></returns>
        public static RefundResult DoRefund(string openId, string outTradeNo, string transactionId, string outRefundNo,
            int totalFee, int refundFee)
        {
            var superOpenIds = ConfigurationManager.AppSettings["OpenIdsForPayMin"]
                .Split(" , ", StringSplitOptions.RemoveEmptyEntries);
            if (superOpenIds.Contains(openId))
            {
                totalFee = 1;
                refundFee = 1;
            }

            //创建请求对象
            var reqHandler = new TenPayV2.TenPay.RequestHandler(HttpContext.Current);

            //通信对象
            var httpClient = new TenPayV2.TenPay.TenpayHttpClient();

            //应答对象
            var resHandler = new TenPayV2.TenPay.ClientResponseHandler();

            //-----------------------------
            //设置请求参数
            //-----------------------------
            reqHandler.init();
            reqHandler.setKey(TenPayInfo.Key);
            reqHandler.setGateUrl("https://api.mch.tenpay.com/refundapi/gateway/refund.xml");

            reqHandler.setParameter("partner", TenPayInfo.PartnerId);
            //out_trade_no和transaction_id至少一个必填，同时存在时transaction_id优先
            reqHandler.setParameter("out_trade_no", outTradeNo);
            reqHandler.setParameter("transaction_id", transactionId);
            reqHandler.setParameter("out_refund_no", outRefundNo);
            reqHandler.setParameter("total_fee", totalFee.ToString());
            reqHandler.setParameter("refund_fee", refundFee.ToString());
            reqHandler.setParameter("op_user_id", TenPayInfo.PartnerId);
            reqHandler.setParameter("op_user_passwd", MD5UtilHelper.GetMD5(TenPayInfo.PartnerId, "GBK"));
            reqHandler.setParameter("service_version", "1.1");

            string requestUrl = reqHandler.getRequestURL();
            httpClient.setCertInfo(@"PartnerCertFile\apiclient_cert.p12", TenPayInfo.PartnerId);
            //设置请求内容
            httpClient.setReqContent(requestUrl);
            //设置超时
            httpClient.setTimeOut(30);

            string rescontent = "";
            //后台调用
            if (httpClient.call())
            {
                //获取结果
                rescontent = httpClient.getResContent();

                resHandler.setKey(TenPayInfo.Key);
                //设置结果参数
                resHandler.setContent(rescontent);

                //创建退款记录日志
                using (var ctx = new HzfyWeiXinEntities())
                {
                    var model = new WxPayRefundLog();
                    model.OutTradeNo = outTradeNo;
                    model.RefundFee = totalFee;
                    model.OutRefundNo = outRefundNo;
                    model.TransactionId = transactionId;
                    model.RecCreateDt = DateTime.Now;
                    model.RefundStatus = 0;
                    ctx.WxPayRefundLog.Add(model);
                    ctx.SaveChanges();
                }

                //判断签名及结果
                if (resHandler.isTenpaySign() && resHandler.getParameter("retcode") == "0")
                {
                    var result = new RefundResult();
                    result.RetCode = resHandler.getParameter("retcode").ToInt32OrDefault();
                    result.RetMsg = resHandler.getParameter("retmsg");
                    result.Partner = resHandler.getParameter("partner");
                    result.TransactionId = resHandler.getParameter("transaction_id");
                    result.OutTradeNo = resHandler.getParameter("out_trade_no");
                    result.OutRefundNo = resHandler.getParameter("out_refund_no");
                    result.RefundId = resHandler.getParameter("refund_id");
                    result.RefundChannel = resHandler.getParameter("refund_channel").ToInt32OrDefault();
                    result.RefundFee = resHandler.getParameter("refund_fee").ToInt32OrDefault();
                    result.RefundStatus = resHandler.getParameter("refund_status").ToInt32OrDefault();
                    result.RecvUserId = resHandler.getParameter("recv_user_id");
                    result.ReccvUserName = resHandler.getParameter("reccv_user_name");

                    //更新退款记录
                    using (var ctx = new HzfyWeiXinEntities())
                    {
                        var model = ctx.WxPayRefundLog.First(x => x.OutRefundNo == outRefundNo);
                        model.RefundId = resHandler.getParameter("refund_id");
                        model.RefundFee = resHandler.getParameter("refund_fee").ToInt32OrDefault();
                        model.RefundChannel = resHandler.getParameter("refund_channel").ToString();
                        //model. = resHandler.getParameter("retmsg");
                        model.RecvUserId = resHandler.getParameter("recv_user_id");
                        model.RecvUserName = resHandler.getParameter("reccv_user_name");
                        model.RefundStatus = resHandler.getParameter("refund_status").ToInt32OrDefault();
                        ctx.SaveChanges();                        
                    }

                    // 用户退款结果通知再执行退款的RMQ中发起
                    return result;
                }
                else
                {
                    //错误时，返回结果未签名。
                    //如包格式错误或未确认结果的，请使用原来订单号重新发起，确认结果，避免多次操作
                    throw new Exception("返回结果未签名");
                }
            }
            else
            {
                //后台调用通信失败
                //有可能因为网络原因，请求已经处理，但未收到应答。
                throw new Exception("调用通信失败");
            }
        }
    }

    public class DeliverNotifyResult
    {
        /// <summary>
        /// errcode
        /// 返回状态码
        /// 返回状态码，0 表示成功，其他未定义
        /// </summary>
        public int ErrorCode { get; set; }

        /// <summary>
        /// errmsg
        /// 返回信息
        /// 返回信息，如非空，为错误原因
        /// </summary>
        public string ErrorMsg { get; set; }
    }

    public class RefundResult
    {
        /// <summary>
        /// retcode
        /// 返回状态码
        /// 返回状态码，0 表示成功，其他未定义
        /// </summary>
        public int RetCode { get; set; }

        /// <summary>
        /// retmsg
        /// 返回信息
        /// 返回信息，如非空，为错误原因
        /// </summary>
        public string RetMsg { get; set; }

        /// <summary>
        /// partner
        /// 商户号
        /// 商户号
        /// </summary>
        public string Partner { get; set; }

        /// <summary>
        /// transaction_id
        /// 财付通订单号
        /// 财付通交易号，28 位长的数值，其中前10 位为商户号，之后8 位为订单产生的日期，如20090415，最后10 位是流水号。
        /// </summary>
        public string TransactionId { get; set; }

        /// <summary>
        /// out_trade_no
        /// 商户订单号
        /// 商户系统内部的订单号
        /// </summary>
        public string OutTradeNo { get; set; }

        /// <summary>
        /// out_refund_no
        /// 商户退款单号
        /// 商户退款单号
        /// </summary>
        public string OutRefundNo { get; set; }

        /// <summary>
        /// refund_id
        /// 财付通退款单号
        /// 财付通退款单号
        /// </summary>
        public string RefundId { get; set; }

        /// <summary>
        /// refund_channel
        /// 退款渠道
        /// 退款渠道,0:退到财付通、1:退到银行
        /// </summary>
        public int RefundChannel { get; set; }

        /// <summary>
        /// refund_fee
        /// 退款金额
        /// 退款总金额,单位为分,可以做部分退款
        /// </summary>
        public int RefundFee { get; set; }

        /// <summary>
        /// refund_status
        /// 退款状态
        /// 退款状态：
        /// 4，10：退款成功。
        /// 3，5，6：退款失败。
        /// 8，9，11：退款处理中。
        /// 1，2：未确定，需要商户原退款单号重新发起。
        /// 7：转入代发，退款到银行发现用户的卡作废或
        ///    者冻结了，导致原路退款银行卡失败，资金回
        ///    流到商户的现金帐号，需要商户人工干预，通
        ///    过线下或者财付通转账的方式进行退款
        /// </summary>
        public int RefundStatus { get; set; }

        /// <summary>
        /// recv_user_id
        /// 接收人帐号
        /// 转账退款接收退款的财付通帐号
        /// </summary>
        public string RecvUserId { get; set; }

        /// <summary>
        /// reccv_user_name
        /// 转账退款接收退款的姓名(需与接收退款的财
        /// 付通帐号绑定的姓名一致)
        /// </summary>
        public string ReccvUserName { get; set; }
    }

    public class RequestHandlerFix : RequestHandler
    {
        public RequestHandlerFix() : base(null)
        { }

        protected override string GetCharset()
        {
            return "UTF-8";
        }

        public new string CreateSHA1Sign()
        {
            StringBuilder sb = new StringBuilder();
            ArrayList akeys = new ArrayList(Parameters.Keys);
            akeys.Sort();

            foreach (string k in akeys)
            {
                string v = (string)Parameters[k];
                if (null != v && "".CompareTo(v) != 0
                       && "sign".CompareTo(k) != 0 && "key".CompareTo(k) != 0)
                {
                    if (sb.Length == 0)
                    {
                        sb.Append(k + "=" + v);
                    }
                    else
                    {
                        sb.Append("&" + k + "=" + v);
                    }
                }
            }

            //建立SHA1对象
            SHA1 sha = new SHA1CryptoServiceProvider();
            //将mystr转换成byte[] 
            byte[] dataToHash = Encoding.UTF8.GetBytes(sb.ToString());
            //Hash运算
            byte[] dataHashed = sha.ComputeHash(dataToHash);
            //将运算结果转换成string
            string hash = BitConverter.ToString(dataHashed).Replace("-", "");

            string paySign = hash.ToLower();

            return paySign;
        }
    }    
}